package org.hawk.gwt.ppc.introspection.impl;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.hawk.gwt.ppc.introspection.JavaArrayTypeScope;
import org.hawk.gwt.ppc.introspection.JavaCallableMember;
import org.hawk.gwt.ppc.introspection.JavaConstructor;
import org.hawk.gwt.ppc.introspection.JavaField;
import org.hawk.gwt.ppc.introspection.JavaGenericTypeScope;
import org.hawk.gwt.ppc.introspection.JavaMethod;
import org.hawk.gwt.ppc.introspection.JavaScope;
import org.hawk.gwt.ppc.introspection.JavaTypeScope;
import org.hawk.gwt.ppc.introspection.JavaTypeVariable;
import org.hawk.gwt.ppc.introspection.JavaVariable;
import org.hawk.gwt.ppc.parser.CompilationUnitDescriptor;
import org.hawk.gwt.ppc.utils.Name;

/**
 * Abstract implementation of java type introspection type.
 * 
 * @author alex.bereznevatiy@gmail.com
 *
 */
abstract class AbstractJavaTypeScope extends JavaScopeImpl implements JavaTypeScope {
	
	private String name;
	private String superName;
	private List<String> interfaces;
	private List<JavaTypeScope> resolvedInterfaces;
	private JavaTypeScope resolvedSuperClass;
	
	AbstractJavaTypeScope(String name, String superName, List<String> interfaces) {
		this.name = name;
		this.superName = superName;
		this.interfaces = interfaces;
	}

	/* (non-Javadoc)
	 * @see org.hawk.gwt.ppc.introspection.JavaTypeScope#getInterfaces()
	 */
	public List<JavaTypeScope> getInterfaces() {
		if (resolvedInterfaces != null) {
			return resolvedInterfaces;
		}
		if (interfaces.isEmpty()) {
			return resolvedInterfaces = Collections.emptyList();
		}
		List<JavaTypeScope> result = new ArrayList<JavaTypeScope>(interfaces.size());
		for (String iface : interfaces) {
			result.add(resolveType(iface, getCompilationUnit()));
		}
		
		return resolvedInterfaces = Collections.unmodifiableList(result);
	}

	/* (non-Javadoc)
	 * @see org.hawk.gwt.ppc.introspection.JavaTypeScope#getSuperClass()
	 */
	public JavaTypeScope getSuperClass() {
		if (superName == null) {
			return null;
		}
		if (resolvedSuperClass == null) {
			resolvedSuperClass = resolveType(superName, getCompilationUnit());
		}
		return resolvedSuperClass;
	}

	/* (non-Javadoc)
	 * @see org.hawk.gwt.ppc.introspection.JavaTypeScope#getName()
	 */
	public String getName() {
		return name;
	}
	
	/*
	 * (non-Javadoc)
	 * @see org.hawk.gwt.ppc.introspection.JavaTypeScope#getField(java.lang.String)
	 */
	public JavaField getField(String name) {
		JavaField result = getDeclaredField(name);
		if (result != null) {
			return result;
		}
		JavaTypeScope superClass = getSuperClass();
		if (superClass != null) {
			return superClass.getField(name);
		}
		return PrimitiveTypeScopes.OBJECT.getField(name);
	}
	
	/**
	 * Searches this class to get field (ignoring superclasses)
	 */
	abstract JavaField getDeclaredField(String name);

	/* (non-Javadoc)
	 * @see org.hawk.gwt.ppc.introspection.JavaTypeScope#getGenericSuperclass()
	 */
	public JavaTypeScope getGenericSuperclass() {
		if (superName == null) {
			return null;
		}
		String params = Name.getTypeParameters(superName);
		if (params.length() == 0) {
			return getSuperClass();
		}
		
		return new JavaParameterizedGenericTypeScopeImpl(getSuperClass(), this, params);
	}

	/* (non-Javadoc)
	 * @see org.hawk.gwt.ppc.introspection.JavaTypeScope#getGenericInterfaces()
	 */
	public List<JavaTypeScope> getGenericInterfaces() {
		List<JavaTypeScope> result = new ArrayList<JavaTypeScope>(interfaces.size());
		for (String iface : interfaces) {
			String params = Name.getTypeParameters(iface);
			if (params.length() == 0) {
				result.add(resolveType(iface, getCompilationUnit()));
			} else {
				result.add(new JavaParameterizedGenericTypeScopeImpl(resolveType(iface, getCompilationUnit()), this, params));
			}
		}
		return Collections.unmodifiableList(result);
	}

	/* (non-Javadoc)
	 * @see org.hawk.gwt.ppc.introspection.JavaTypeScope#getDependingTypes()
	 */
	public List<String> getDependingTypes() {
		Set<String> result = new HashSet<String>();
		collectHierarchy(this, result);
		
//		for (JavaTypeScope type : getDeclaredClasses()) {
//			result.add(type.getName());
//			collectHierarchy(type, result);
//		}
		
		for (JavaMethod m : getMethods()) {
			result.add(m.getReturnType().getName());
			collectCallable(m, result);
		}
		
		for (JavaConstructor c : getConstructors()) {
			collectCallable(c, result);
		}
		
		return Collections.unmodifiableList(new ArrayList<String>(result));
	}
	
	private void collectCallable(JavaCallableMember member, Set<String> result) {
		JavaTypeScope last = null;
		for (JavaTypeScope type : member.getParametersTypes()) {
			last = type;
			result.add(type.getName());
		}
		if (last != null && member.isVarArgs()) {
			result.add(((JavaArrayTypeScope)last).getComponentType().getName());
		}
	}
	
	private void collectHierarchy(JavaTypeScope type, Set<String> result) {
		JavaTypeScope superType = type.getSuperClass();
		if (superType != null) {
			result.add(superType.getName());
			collectHierarchy(superType, result);
		}
		for (JavaTypeScope iface : type.getInterfaces()) {
			result.add(iface.getName());
			collectHierarchy(iface, result);
		}
	}

	/* (non-Javadoc)
	 * @see org.hawk.gwt.ppc.introspection.impl.JavaScopeImpl#getBestMatchMethod(java.lang.String, java.util.List)
	 */
	@Override
	public JavaMethod getBestMatchMethod(String name, List<JavaTypeScope> arguments) {
		int resultRelevance = 0;
		JavaMethodImpl result = null;
		for (JavaMethod m : getMethods()) {
			JavaMethodImpl method = (JavaMethodImpl)m;
			int relevance = method.match(name, arguments);
			if (relevance == JavaMethodImpl.NO_RELEVANCE) {
				continue;// no match
			}
			if (relevance == JavaMethodImpl.FULL_RELEVANCE) {
				return method;// found exact match
			}
			if (resultRelevance < relevance) {
				result = method;// found partial relevance
				resultRelevance = relevance;
			}
		}
		JavaMethodImpl parentResult = (JavaMethodImpl) super.getBestMatchMethod(name, arguments);
		if (parentResult != null) {
			int relevance = parentResult.match(name, arguments);
			if (relevance == JavaMethodImpl.FULL_RELEVANCE) {
				return parentResult;
			}
			if (relevance > resultRelevance) {
				result = parentResult;
				resultRelevance = relevance;
			}
		}
		JavaTypeScope superClass = getSuperClass();
		if (superClass != null) {
			JavaMethodImpl superResult = (JavaMethodImpl) superClass.getBestMatchMethod(name, arguments);
			if (superResult != null) {
				int relevance = superResult.match(name, arguments);
				if (relevance == JavaMethodImpl.FULL_RELEVANCE) {
					return superResult;
				}
				if (relevance > resultRelevance) {
					result = superResult;
					resultRelevance = relevance;
				}
			}
		}
		
		if (resultRelevance == JavaMethodImpl.NO_RELEVANCE) {
			return null;
		}
		
		return result;
	}

	/* (non-Javadoc)
	 * @see org.hawk.gwt.ppc.introspection.JavaTypeScope#isAssignableFrom(org.hawk.gwt.ppc.introspection.JavaTypeScope)
	 */
	public boolean isAssignableFrom(JavaTypeScope from) {
		if (from == null) {
			return false;
		}
		if (this.equals(from)) {
			return false;
		}
		for (JavaTypeScope iface : getInterfaces()) {
			if (iface.isAssignableFrom(from)) {
				return true;
			}
		}
		
		JavaTypeScope superType = getSuperClass();
		return superType != null && superType.isAssignableFrom(from);
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}
		if (obj == null) {
			return false;
		}
		JavaTypeScope scope;
		if (obj instanceof JavaGenericTypeScope) {
			scope = ((JavaGenericTypeScope)obj).getRawType();
		} else if (obj instanceof JavaTypeScope) {
			scope = (JavaTypeScope)obj;
		} else {
			return false;
		}
		return getName().equals(scope.getName());
	}

	/* (non-Javadoc)
	 * @see org.hawk.gwt.ppc.introspection.impl.JavaScopeImpl#resolveType(java.lang.String, org.hawk.gwt.ppc.parser.CompilationUnitDescriptor)
	 */
	@Override
	JavaTypeScope resolveType(String name, CompilationUnitDescriptor context) {
		if (name.endsWith("[]")) {
			name = name.substring(0, name.length() - 2);
			JavaTypeScope element = resolveType(name, context);
			return element instanceof JavaGenericTypeScope ? 
					new JavaArrayGenericTypeScopeImpl(element, this) : 
					new JavaArrayTypeScopeImpl(element);
		}
		for (JavaTypeVariable var : getTypeVariables()) {
			if (var.getName().equals(name)) {
				return var;
			}
		}
		if (Name.isBinaryJava(name)) {
			name = Name.fromBinary(name);
		}
		
		name = Name.getRawName(name);
		
		if (name.equals(Name.getSimpleName(getName()))) {
			return this;
		}
		
		for (JavaTypeScope type : getDeclaredClasses()) {
			if (type.getName().equals(name) ||
				Name.getSimpleName(type.getName()).equals(name)) {
				return type;
			}
		}
		return super.resolveType(name, context);
	}

	/* (non-Javadoc)
	 * @see org.hawk.gwt.ppc.introspection.impl.JavaScopeImpl#resolveTypeParameter(java.lang.String)
	 */
	@Override
	JavaTypeVariable resolveTypeParameter(String paramName) {
		for (JavaTypeVariable var : getTypeVariables()) {
			if (var.getName().equals(paramName)) {
				return var;
			}
		}
		return super.resolveTypeParameter(paramName);
	}

	/* (non-Javadoc)
	 * @see org.hawk.gwt.ppc.introspection.impl.JavaScopeImpl#getChildren()
	 */
	@Override
	public List<JavaScope> getChildren() {
		List<JavaScope> children = super.getChildren();
		List<JavaMethod> methods = getMethods();
		List<JavaConstructor> constructors = getConstructors();
		List<JavaTypeScope> classes = getDeclaredClasses();
		List<JavaScope> result = new ArrayList<JavaScope>(children.size() +  
				methods.size() + constructors.size() + classes.size());
		result.addAll(children);
		result.addAll((Collection<? extends JavaScope>) methods);
		result.addAll((Collection<? extends JavaScope>) constructors);
		result.addAll((Collection<? extends JavaScope>) classes);
		return Collections.unmodifiableList(result);
	}

	/* (non-Javadoc)
	 * @see org.hawk.gwt.ppc.introspection.impl.JavaScopeImpl#getVariable(java.lang.String, int, int)
	 */
	@Override
	public JavaVariable getVariable(String name, int line, int offset) {
		JavaVariable result = getField(name);
		if (result != null) {
			return result;
		}
		return super.getVariable(name, line, offset);
	}

	/*
	 * (non-Javadoc)
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		return getName();
	}
}
